package Lamport;
/**
 * 
 */

/**
 * @author conloguern3
 *
 */


import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.Timer;
import javax.swing.WindowConstants;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.UIManager;
import javax.swing.SwingUtilities;

@SuppressWarnings("unused")
public class GUI extends JPanel implements Runnable, ActionListener {

	private static final long serialVersionUID = -2680375415706884365L;

	private JFrame window;

	public String version = "1.0.0";

	JFrame frame;

	private JPopupMenu menu;

	private Timer timer;

	private JButton connect, host;

	private static JButton mode;

	private static JButton request;

	private JButton quit;

	public static boolean controlResource;

	public static boolean automaticMode;

	private JLabel resourcePercentage, timeConnected;

	public int height = 900;

	public int width = 900;

	public double timeControlled, timeNotControlled, totalTime;

	public double startTime = System.currentTimeMillis();

	public static ArrayList<Hostname> nodeArray;
	
	public static ArrayList<Hostname> connectArray;

	public static Scanner scan;

	public static Box resourceBox;

	public Box info;

	public static NodeGenerator iconGenerator = new NodeGenerator();

	private DecimalFormat form = new DecimalFormat( "00" );

	public static File file;
	
	

	

	// Added By Ryan ---------------------------------
	public static int lamportTimer = 0;

	private MultiServer ms = new MultiServer();
	Thread sThread = new Thread(ms.serverRunnable);

	public static LamportProtocol lp = new LamportProtocol();

	public static LamportClient[] clientList;
	public Thread[] threadList;
	// ------------------------------------------------

	public void run() {
		createAndShowGUI();//create GUI
		init(); //initial conditions
		try {
			readInHosts();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		startServer();
		
		play();  //start timer
	}

	private void createAndShowGUI() {
		setBackground(Color.LIGHT_GRAY);

		menu = new JPopupMenu();
		// Adding buttons onto GUI
		connect = new JButton();
		connect.setText("Connect");
		connect.addActionListener(this);
		add(connect);

		host = new JButton();
		host.setText("Host");
		host.addActionListener(this);
		add(host);

		mode = new JButton();
		mode.setText("Automatic Mode");
		mode.addActionListener(this);
		add(mode);

		request = new JButton();
		request.setText("Request Resource");
		request.addActionListener(this);
		add(request);

		quit = new JButton();
		quit.setText("Quit");
		quit.addActionListener(this);
		add(quit);

		resourceBox = new Box(BoxLayout.X_AXIS);
		resourceBox.setBorder(BorderFactory.createTitledBorder("Resources"));
		resourceBox.setPreferredSize(new Dimension(width, 350));
		add(resourceBox);

		resourcePercentage = new JLabel();
		timeConnected = new JLabel();

		Box stats = new Box(BoxLayout.Y_AXIS);
		stats.setBorder(BorderFactory.createTitledBorder("Statistics"));
		stats.setPreferredSize(new Dimension(width-250, 350));
		stats.add(resourcePercentage);
		stats.add(timeConnected);		
		add(stats);

		info = new Box(BoxLayout.Y_AXIS);
		info.setBorder(BorderFactory.createTitledBorder("Information"));
		info.setPreferredSize(new Dimension(230, 350));

		JLabel title = new JLabel();
		title.setText("Resource Request Ver "+getVersion()+".");
		info.add(title);

		JLabel authors = new JLabel("\nCreators:");
		info.add(authors);

		JLabel jeremy = new JLabel("Jeremy Towne (GUI)");
		JLabel ryan = new JLabel("Ryan Conlouge (Networking)");
		JLabel john = new JLabel("John Tkach (System)");
		info.add(jeremy);
		info.add(ryan);
		info.add(john);		

		add(info);

		window = new JFrame("Resource");
		window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		window.add(menu);
		window.getContentPane().add(this);
		window.pack();
		window.setLocationRelativeTo(null);
		window.setVisible(true);

		timer = new Timer(1, this);

	}

	public void startServer(){
		sThread.start();
	}


	@SuppressWarnings({ "unchecked", "rawtypes", "null" })
	public void readInHosts() throws FileNotFoundException{
		
		InetAddress localaddr = null;
		try {
			localaddr = InetAddress.getLocalHost();
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		String scanned = null;
		int host_index = 0;

		InputStream hostsURL = getClass().getResourceAsStream("hosts");	
			BufferedReader br = null;
			try {
				br = new BufferedReader(new InputStreamReader(hostsURL, "UTF-8"));
			} catch (UnsupportedEncodingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			openFile();

		//sync array with text file
		while(scan.hasNext()){
			String tempHostname = scan.next();
			String tempIPAddress = scan.next();
			if(tempHostname.equalsIgnoreCase(localaddr.getHostName())){
			nodeArray.add(new Hostname(tempHostname, 1));
			}
			else{
				nodeArray.add(new Hostname(tempHostname, 1));
				connectArray.add(new Hostname(tempIPAddress, 1));
		}
		}
				
		}


		public Dimension getPreferredSize() {
			int height = 600;
			int width = 900;
			return new Dimension(width, height);
		}

		/**
		 * Actions to perform when buttons are pressed
		 */
		public void actionPerformed(ActionEvent e) {
			if(e.getSource() == timer) {
				step();
			} else if(e.getSource() == connect) {
				connect();
			} else if(e.getSource() == host) {
				host();
			} else if(e.getSource() == mode) {
				if(mode.getText().equals("Automatic Mode")) {
					try {
						setAutomaticMode();
					} catch (UnknownHostException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					} catch (IOException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				} else if(mode.getText().equals("Manual Mode")) {
					setManualMode();
				} else {
					try {
						quit();
					} catch (UnknownHostException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					} catch (IOException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}
			} else if(e.getSource() == request) {
				if(request.getText().equals("Request Resource")) {
					try {
						requestResource();
					} catch (IOException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}    
				} else if(request.getText().equals("Release Resource")) {
					try {
						releaseResource();
					} catch (UnknownHostException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					} catch (IOException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				} else {
					try {
						quit();
					} catch (UnknownHostException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					} catch (IOException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}
			} else if(e.getSource() == quit) {
				try {
					quit();
				} catch (UnknownHostException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}

			repaint();

		}

		/**
		 * Opens the text file of host names and creates a Scanner (scan) on it
		 * @throws FileNotFoundException
		 */
		public void openFile() throws FileNotFoundException {
			//	file = new File("./src/Lamport/hosts");
				InputStream hostsURL = getClass().getResourceAsStream("hosts");
				scan = new Scanner(hostsURL);
			}

		/**
		 * Called by the timer - updates statistics and GUI
		 */
		private void step() {
			updateStats();

			repaint();
		}

		/**
		 * Function getVersion
		 * @return (String) The current version of the system
		 */
		public String getVersion() {
			return this.version;
		}

		/**
		 * Function updateStats - Updates all the statistics of the system and displays them in the Statistics Box
		 */
		private void updateStats() {
			//updating percentage controlling resource
			if(controlResource)
				timeControlled++;
			else
				timeNotControlled++;
			totalTime++;
			resourcePercentage.setText("Proportion of time controlling resource: %"+ 
					form.format(timeControlled/totalTime*100)/* +
				"\n% of time not controlling resource: "+
				form.format(timeNotControlled/totalTime*100)*/);
			double temp = System.currentTimeMillis() - startTime;
			timeConnected.setText("Time connected: "+ form.format(temp/1000/60/60 % 24) +":"+ (form.format(Math.floor(temp/1000/60 % 60)))+":"+ form.format(temp/1000 %60));
		}


		private void connect() {
			int i = 0;
			ArrayList<LamportClient> clientAL = new ArrayList<LamportClient>();
			ArrayList<Thread> threadAL = new ArrayList<Thread>();

			while(i < connectArray.size()){
				LamportClient tempClient = new LamportClient(connectArray.get(i).getName());
				Thread tempClientThread = new Thread(tempClient.clientRunnable);

				clientAL.add(tempClient);
				threadAL.add(tempClientThread);
				i++;
			}
			clientList = new LamportClient[clientAL.size()];
			for(int j = 0; j < clientList.length; j++){
				clientList[j] = clientAL.get(j);
			}		
			threadList = new Thread[threadAL.size()];
			for(int j = 0; j < clientList.length; j++){
				threadList[j] = threadAL.get(j);
			}
	//		System.out.println(threadList[0]);

			for(int j = 0; j < threadList.length; j++){
				threadList[j].start();;
			}
		}

		private void host() {
			//	sThread.start();
			JOptionPane.showMessageDialog(frame, "Hosts are Connecting...");
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}

		private static void quit() throws UnknownHostException, IOException {
			InetAddress localaddr = null;
			try {
				localaddr = InetAddress.getLocalHost();
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			incrementTimer(2);
			//Message to be sent
			int tempTimer = lamportTimer;
			String message = (tempTimer + " Quit " + localaddr.getHostName());
			System.out.println(message);
			//Sends Message over the socket
			if(clientList.length == 0){
				System.exit(0);
			}else{
			
			
			for(int i = 0; i < clientList.length; i++ ){
				System.out.println(clientList[i]);
				clientList[i].out.println(message);
			}
			//Parse the response, usually an Acknowledge
			for(int i = 0; i < clientList.length; i++ ){
				String outputLine = lp.processInput(clientList[i].in.readLine());
			}

			System.exit(0);
			}
		}

		private void play() {
			timer.start();
		}

		static void setAutomaticMode() throws UnknownHostException, IOException {

			InetAddress localaddr = null;
			int ackReceived = 0;
			try {
				localaddr = InetAddress.getLocalHost();
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			incrementTimer(2);
			//Message to be sent
			automaticMode = true;
			int tempTimer = lamportTimer;
			String message = (tempTimer + " Auto " + localaddr.getHostName());
			System.out.println(message);
			//Sends Message over the socket
			for(int i = 0; i < clientList.length; i++ ){
				System.out.println(clientList[i]);
				clientList[i].out.println(message);
			}
			while(automaticMode){
				for(int i = 0; i < 100; i++){
				requestResource();
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				updateGUI();
				releaseResource();
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				updateGUI();
			}
				automaticMode = false;
			}


			//	lc1.out.println(message);
			//Parse the response, usually an Acknowledge
			String outputLine = null;
			for(int i = 0; i < clientList.length; i++ ){
				outputLine = lp.processInput(clientList[i].in.readLine());
				if(outputLine.equalsIgnoreCase("Acknowledgement Received " + localaddr.getHostName())){
					ackReceived++;
				}
			}

			//String outputLine = lp.processInput(lc1.in.readLine());
			System.out.println(outputLine);
			if(ackReceived == (clientList.length)){
				aquireResource();
				changeNode(localaddr.getHostName());
			}
			else{
				System.out.println("Not Enough Ack to grant Resource");
			}


			mode.setText("Manual Mode");
			request.setVisible(false);
			updateGUI();
		}

		private void setManualMode() {
			mode.setText("Automatic Mode");
			request.setVisible(true);
			repaint();
		}

		private static void requestResource() throws IOException {
			
			InetAddress localaddr = null;
			int ackReceived = 0;
			try {
				localaddr = InetAddress.getLocalHost();
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			updateGUI();
			incrementTimer(5);
			//Message to be sent
			int tempTimer = lamportTimer;
			String message = (tempTimer + " Req " + localaddr.getHostName());
			System.out.println(message);
			//Sends Message over the socket
			for(int i = 0; i < clientList.length; i++ ){
				System.out.println(clientList[i]);
				clientList[i].out.println(message);
			}
			//	lc1.out.println(message);
			//Parse the response, usually an Acknowledge
			String outputLine = null;
			for(int i = 0; i < clientList.length; i++ ){
				outputLine = lp.processInput(clientList[i].in.readLine());
				if(outputLine.equalsIgnoreCase("Acknowledgement Received " + localaddr.getHostName())){
					ackReceived++;
				}
			}
			int cList = clientList.length;
			//String outputLine = lp.processInput(lc1.in.readLine());
			System.out.println(outputLine);
			if(ackReceived == (clientList.length)){
				System.out.println("Resource Granted");
				aquireResource();
				
		//		mode.setVisible(true);
			}
			else{
				System.out.println("Not Enough Ack to grant Resource");
			}
		}

		private static void aquireResource() {
			InetAddress localaddr = null;
			try {
				localaddr = InetAddress.getLocalHost();
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			request.setText("Release Resource");
			controlResource = true;
			changeNode(localaddr.getHostName());
			updateGUI();
		}

		private static void relResource() {
			InetAddress localaddr = null;
			int ackReceived = 0;
			try {
				localaddr = InetAddress.getLocalHost();
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			request.setText("Request Resource");
			controlResource = false;
			changeNode(localaddr.getHostName());
			updateGUI();
		}

		private static void releaseResource() throws UnknownHostException, IOException {
			
			InetAddress localaddr = null;
			try {
				localaddr = InetAddress.getLocalHost();
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			incrementTimer(2);
			//Message to be sent
			int tempTimer = lamportTimer;
			String message = (tempTimer + " Rel " + localaddr.getHostName());
			System.out.println(message);
			//Sends Message over the socket
			int relReceived = 0;


			for(int i = 0; i < clientList.length; i++ ){
				clientList[i].out.println(message);
			}

			//Parse the response, usually an Acknowledge
			String outputLine = null;
			System.out.println(clientList.length);
			for(int j = 0; j < (clientList.length); j++ ){
				outputLine = lp.processInput(clientList[j].in.readLine());
				if(outputLine.equalsIgnoreCase("Release Acknowlegement Received")){
					relReceived++;
				}
			}
			int clientListPercent = (int) ((clientList.length) * .5);
			
			System.out.println(outputLine);
			if(relReceived > (clientListPercent)){
				relResource();		
			}

		}

		private void init() {
			nodeArray = new ArrayList<Hostname>();
			connectArray = new ArrayList<Hostname>();
			timer.start();
			timeControlled = 0;
			timeNotControlled = 0;
			totalTime = 0;
			controlResource = false;
			repaint();
		}

		public int getNumProcess() {
			return nodeArray.size();
		}

		public boolean getProcessStatus(String owner) {
			if(owner.equals("hostname 0"))
				return true;
			else
				return false;
		}

		/**
		  * Function changeNode
		  * @param hostname (String) - The host name of the node we are changing
		  */
		 public static void changeNode(String hostname) {
		for(Hostname node : nodeArray) {
		 if(node.getName().equals(hostname))
		 node.changeControl();
		 updateGUI();
		 }


		 


		 }


		/**
		 * Function addNode - adds a node to the display
		 * @param hostname (String) - the hostname to be displayed on the node
		 * @param type (int) - 0 = green node, 1 = red node
		 * @throws IOException 
		 * @throws UnknownHostException 
		 */
		public static void addNodeToGUI(String hostname, int type) throws UnknownHostException, IOException {

			if(type == 0) { //green
				resourceBox.add(iconGenerator.createGreenIcon(hostname));
			}
			else if (type == 1) //red
				resourceBox.add(iconGenerator.createRedIcon(hostname));
			else {
				System.out.println("Error - Incorrect node type specified");
				quit();
			}

			resourceBox.repaint();

		}

		/**
		 * Function removeNode - removes all nodes from the display with the given host name
		 * @param hostname (String) - the hostname of the node to be removed
		 * @return (String) - Used to identify if a red or green node was deleted
		 */
		public static String removeNode(String hostname) {

			for (Hostname node : nodeArray) {
				if (node.getName().equals(hostname)) {
					nodeArray.remove(node);
					break;
				}
			}

			for(int i = 0; i<resourceBox.getComponents().length;i++) {
				JLabel test = ((JLabel) resourceBox.getComponent(i));
				if(test.getText().equals(hostname)){
					resourceBox.remove(test);
					return test.getToolTipText();				
				}
			}

			resourceBox.repaint();
			return null;

		}

		/**
		 * Function updateHosts - syncs the internal hosts with the external text file
		 * @throws IOException 
		 * @throws UnknownHostException 
		 */
		public static void updateHosts() throws UnknownHostException, IOException {


			 boolean found;


			 //making sure all of the nodes in the list are represented on the GUI 
			 for(Hostname node : nodeArray) {
			 found = false;
			 for(int i = 0; i<resourceBox.getComponents().length;i++) {
			 JLabel test = ((JLabel) resourceBox.getComponent(i));
			 if(test.getText().equals(node.getName()) && equalTypes(node, test)) {
			 found = true;
			 break;
			 } 
			 }


			 if(!found)
			 addNodeToGUI(node.getName(), node.getControl());
			 }




			 //making sure all of the nodes represented on the GUI are in the list
			 for(int i = 0; i<resourceBox.getComponents().length;i++) {
			 found = false;
			 JLabel test = ((JLabel) resourceBox.getComponent(i));
			 for(Hostname node : nodeArray) {
			 if(node.getName().equals(test.getText()) && equalTypes(node, test)) {
			 found = true;
			 break;
			 } 
			 }


			 if(!found)
			 resourceBox.remove(test); 
			 }
			 


			 resourceBox.repaint();


			 }

		/**
		 * Determines if the resource is being controlled by a process
		 * @return true if it is being controlled, false otherwise
		 */
		public boolean resourceControlled() {
			for(Hostname node : nodeArray) {
				if(node.getControl() == 0)
					return true;
			}
			return false;
		}


		/**
		 * paintComponent
		 * Creates objects to be shown on the GUI
		 *@param g
		 */
		public void paintComponent(Graphics g) {
			super.paintComponent(g);

			try {
				updateHosts();
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}
		
		public static void updateGUI(){
			try {
				updateHosts();
			} catch (UnknownHostException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}
		
		
		public static boolean equalTypes(Hostname node, JLabel test) {
			 if(node.getControl() == 1 && test.getToolTipText().equals(node.getName()+" is NOT controlling the resource"))
			 return true;
			 else if(node.getControl() == 0 && test.getToolTipText().equals(node.getName()+" is controlling the resource"))
			 return true;
			 else
			 return false; 
			}

		public int getTimer(){

			return lamportTimer;
		}

		public static void setTimer(int newTimer){
			lamportTimer = newTimer;
		}

		public static void incrementTimer(int incrementVal){
			lamportTimer = lamportTimer + incrementVal;
		}

	}

